
#pragma once

#include "glm\glm.hpp"
#include "glm\ext.hpp"
using namespace std;
#include "Shader.h"
#include "Camera.h"
#include "VertexBuffer.h"
#include "Mesh.h"
#include "Helpers.h"


struct render_params
{
	mat4 projectionMatrix;
	mat4 viewProjectionMatrix;
	Shader* shader;
	GLuint colorTexId;
	GLuint depthTexId;

	render_params()
	{
		clear();
	}

	void clear()
	{
		projectionMatrix = mat4(1.0f);
		viewProjectionMatrix = mat4(1.0f);
		shader = nullptr;
	}
};


class INode
{
public:
	virtual void render(render_params* params)
	{
		for (auto el : _childs)
			el->render(params);
	}

	virtual void addChild(INode* node)
	{
		assert(node != this && node != nullptr);

		_childs.push_back(node);
		node->_parent = this;
	}

	virtual INode* getChildWithIndex(int index)
	{
		assert(index >= 0);

		return _childs[index];
	}

	virtual INode* getChildWithName(string& name, bool recourcively)
	{
		INode* res = nullptr;

		for (auto el : _childs) {
			if (el->_name == name) {
				res = el;
				break;
			} 
			else if (recourcively) {
				res = el->getChildWithName(name, recourcively);
				if (res != nullptr)
					break;
			}
		}

		return res;
	}

protected:
	INode* _parent;
	vector<INode*> _childs;
	string _name;
	int _id;
};


class ProjectionNode : public INode
{
public:
	ProjectionNode(const mat4& projectionMatrix)
	{
		_projectionMatrix = projectionMatrix;
	}

	virtual void render(render_params* params)
	{
		params->projectionMatrix = _projectionMatrix;

		INode::render(params);
	}

private:
	mat4 _projectionMatrix;
};


class ViewNode : public INode
{
public:
	ViewNode(Camera* camera)
	{
		assert(camera != nullptr);

		_camera = camera;
	}

	void render(render_params* params)
	{
		params->viewProjectionMatrix = params->projectionMatrix * _camera->getViewMatrix();

		INode::render(params);
	}

private:
	Camera* _camera;
};


class ShaderNode : public INode
{
public:
	ShaderNode(Shader* shader)
	{
		assert(shader != nullptr);

		_shader = shader;
	}

	virtual void render(render_params* params)
	{
		_shader->bind();

		params->shader = _shader;
		INode::render(params);

		_shader->unBind();
	}

private:
	Shader* _shader;
};


class MeshNode : public INode
{
public:
	MeshNode(GLuint primitiveType)
	{
		_primitiveType = primitiveType;
	}

	virtual void render(render_params* params)
	{
		mat4 mvpMatrix = params->viewProjectionMatrix * _mesh->getModelMatrix();
		glUniformMatrix4fv(params->shader->getUniformLocation("mvpMatrix"), 1, GL_FALSE, value_ptr(mvpMatrix));

		glBindVertexArray(_vao);
		glDrawArrays(_primitiveType, 0, _vertexCount);
		glBindVertexArray(0);
	}

	void setMesh(Mesh* mesh)
	{
		assert(mesh != nullptr);

		_mesh = mesh;
		createBuffers();
	}

public:
	VertexBuffer* _vertexBuffer;
	mat4 _transform;

	// new
	Mesh* _mesh;

	GLuint _primitiveType;
	GLuint _vao;
	GLuint* _vbo;
	int _vboCount;
	int _vertexCount;

	void createBuffers()
	{
		VertexBufferDescr& vbd = _mesh->getVertexBufferDescr();
		_vboCount = vbd.vertexAttribsCount;
		_vertexCount = vbd.vertexCount;
		_vbo = new GLuint[_vboCount];

		glGenVertexArrays(1, &_vao);
		glBindVertexArray(_vao);

		glGenBuffers(_vboCount, _vbo);

		for (int i = 0; i < _vboCount; i++) {
			VertexAttribDescr& ad = vbd.vertexDescrPtr[i];
			int vertexAttribSize = ad.componentCount * getOglTypeSize(ad.type);
			glBindBuffer(GL_ARRAY_BUFFER, _vbo[i]);
			glBufferData(GL_ARRAY_BUFFER, _vertexCount * vertexAttribSize, _mesh->getDataWithAttribId(ad.attribId), ad.drawMode);

			glEnableVertexAttribArray(ad.attribId);
			glVertexAttribPointer(ad.attribId, ad.componentCount, ad.type, GL_FALSE, vertexAttribSize, BUFFER_OFFSET(0));
		}

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
	}

	int getOglTypeSize(GLuint typeId)
	{
		switch (typeId)
		{
		case GL_FLOAT:
			return sizeof(GLfloat);
		}

		assert(false);

		return UNDEF;
	}
};


class FrameBufferNode : public INode
{
public:
	FrameBufferNode(GLuint width, GLuint height)
	{
		_colorTextureId = helper::texture::getEmptyTextureRGBA8(width, height);
		_depthTextureId = helper::texture::getDepthTexture(width, height);

		//glGenRenderbuffers(1, &_rbo);
		//glBindRenderbuffer(GL_RENDERBUFFER_EXT, _rbo);
		//glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height);
		//glBindRenderbuffer(GL_RENDERBUFFER_EXT, 0);

		glGenFramebuffers(1, &_fbo);
		glBindFramebuffer(GL_FRAMEBUFFER, _fbo);

		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _colorTextureId, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,  GL_TEXTURE_2D, _depthTextureId, 0);
		//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _rbo);

		GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		assert(status == GL_FRAMEBUFFER_COMPLETE);

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}

	virtual void render(render_params* params)
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);

		glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		INode::render(params);

		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

		params->colorTexId = _colorTextureId;
		params->depthTexId = _depthTextureId;
	}

private:
	GLuint _colorTextureId;
	GLuint _depthTextureId;
	GLuint _fbo;
	GLuint _rbo;
};


class TempNode : public INode
{
public:
	TempNode()
	{
		float size = 1.0;
		float pos[12] = {-size, -size, 0.0,
						  -size,  size, 0.0,
						  size, -size, 0.0,
						  size,  size, 0.0};

		float tc[8]   = {0.0, 0.0,
						 0.0, 1.0,
						 1.0, 0.0,
						 1.0, 1.0 };

		glGenVertexArrays(1, &_vao);
		glBindVertexArray(_vao);

		glGenBuffers(1, &_vboPos);
		glBindBuffer(GL_ARRAY_BUFFER, _vboPos);
		glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), pos, GL_STATIC_DRAW);
		glEnableVertexAttribArray(ATTRIB_POSITION);
		glVertexAttribPointer(ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), BUFFER_OFFSET(0));

		glGenBuffers(1, &_vboTc);
		glBindBuffer(GL_ARRAY_BUFFER, _vboTc);
		glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(float), tc, GL_STATIC_DRAW);
		glEnableVertexAttribArray(ATTRIB_TEXCOORD0);
		glVertexAttribPointer(ATTRIB_TEXCOORD0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), BUFFER_OFFSET(0));

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
	}

	virtual void render(render_params* params)
	{
		glBindTexture(GL_TEXTURE_2D, params->colorTexId);

		glBindVertexArray(_vao);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
		glBindVertexArray(0);

		glBindTexture(GL_TEXTURE_2D, 0);
	}

private:
	GLuint _vao;
	GLuint _vboPos;
	GLuint _vboTc;
};